﻿///search for best move
namespace FSharpChess
type entry_t =
    {
       mutable lock:uint32;
       mutable emove:uint16;
       mutable depth:int8;
       mutable edate:uint8;
       mutable move_depth:int8;
       flags:uint8;
       mutable min_depth:int8;
       mutable max_depth:int8;
       mutable min_value:int16;
       mutable max_value:int16;
    }
type trans_t =
     { 
       table:entry_t[];
       mutable size:uint32;
       mutable mask:uint32;
       mutable tdate:uint8;
       age:int[];
       mutable used:uint32;
       mutable read_nb:int64;
       mutable read_hit:int64;
       mutable write_nb:int64;
       mutable write_hit:int64;
       mutable write_collision:int64;
    }
module Trans =
    let UseModulo = false
    let DateSize = 16uy
    let ClusterSize = 4
    let DepthNone = -128y
    let MoveNone = 0us
    let ValueInf = 30000s
    let clear_entry =
        {
            lock = 0u;
            emove = MoveNone;
            depth = DepthNone;
            edate = 0uy;
            move_depth = DepthNone;
            flags = 0uy;
            min_depth = DepthNone;
            max_depth = DepthNone;
            min_value = -ValueInf;
            max_value = +ValueInf;
        }
    let trans = 
        let target = 16 * 1024 * 1024
        let sze = target/sizeof<entry_t>
        let size = sze + (ClusterSize - 1) // HACK to avoid testing for end of table
        let mask = sze - 1
        let table = Array.create size clear_entry
        let age = Array.create (int(DateSize)) 0
        { 
            table=table;
            size=uint32(size);
            mask=uint32(mask);
            tdate=0uy;
            age=age;
            used=0u;
            read_nb=0L;
            read_hit=0L;
            write_nb=0L;
            write_hit=0L;
            write_collision=0L;
        }
        
    let entry_is_ok entry =
        entry.edate < DateSize && 
        not (entry.emove = MoveNone && entry.move_depth <> DepthNone) &&
        not (entry.emove <> MoveNone && entry.move_depth = DepthNone) &&
        not (entry.min_value = -ValueInf && entry.min_depth <> DepthNone) &&
        not (entry.min_value > -ValueInf && entry.min_depth = DepthNone) &&        
        not (entry.max_value = ValueInf && entry.max_depth <> DepthNone) &&
        not (entry.max_value < ValueInf && entry.max_depth = DepthNone)
    let trans_entry key =
        if UseModulo then
            uint32(key) % (trans.mask + 1u)
        else 
            uint32(key) &&& trans.mask
    let trans_stats () =
       let full = double(trans.used) / double(trans.size)
       System.Diagnostics.Debug.WriteLine("info hashfull " + (full*1000.0).ToString())
    let trans_retrieve (key:uint64) =
        // init
        trans.read_nb<-trans.read_nb+1L
        // probe
        let ent_index = int(trans_entry key)
        let rec getmv indx ct =
            if ct=ClusterSize then
                false,0us,-1y,-1y,-1s,-1s
            else
                let ent = trans.table.[indx]
                if ent.lock=uint32(key) then
                    true,ent.emove,ent.min_depth,ent.max_depth,ent.min_value,ent.max_value
                else
                    getmv (indx+1) (ct+1)
        getmv ent_index 0
    let trans_store key mv depth min_value max_value =
        // init
        trans.write_nb <- trans.write_nb + 1L
        // probe
        let ent_index = trans_entry key
        let rec probemv indx ct best_score best_entry =
            let ent = trans.table.[indx]
            if ent.lock=uint32(key) then
                trans.write_hit <- trans.write_hit + 1L
                if ent.edate <> trans.tdate then trans.used <- trans.used + 1u
                ent.edate <- trans.tdate
                if depth > ent.depth then ent.depth <- depth // for replacement scheme
                if mv <> MoveNone && depth >= ent.move_depth then
                    ent.move_depth <- depth
                    ent.emove <- mv
                if min_value > -ValueInf && depth >= ent.min_depth then
                    ent.min_depth <- depth
                    ent.min_value <- min_value
                if max_value < ValueInf && depth >= ent.max_depth then
                    ent.max_depth <- depth
                    ent.max_value <- max_value
                ent
            else
                let score = trans.age.[int(ent.edate)] * 256 - int(ent.depth)
                if score > best_score then
                    if ct = ClusterSize-1 then ent else probemv (indx+1) (ct+1) score ent
                else
                    if ct = ClusterSize-1 then best_entry else probemv (indx+1) (ct+1) best_score best_entry
        // "best" entry found
        let entry = probemv (int(ent_index)) 0 -32767 trans.table.[int(ent_index)]
        if entry.edate = trans.tdate then
            trans.write_collision <- trans.write_collision + 1L
        else
            trans.used <- trans.used + 1u
        // store
        entry.lock <- uint32(key)
        entry.edate <- trans.tdate
        entry.depth <- depth
        entry.move_depth <- if mv <> MoveNone then depth else DepthNone
        entry.emove <- mv
        entry.min_depth <- if min_value > -ValueInf then depth else DepthNone
        entry.max_depth <- if max_value < ValueInf then depth else DepthNone
        entry.min_value <- min_value
        entry.max_value <- max_value
    let trans_age date =
        let age = trans.tdate - date
        if age < 0uy then age + DateSize else age
    let trans_set_date date =
        trans.tdate <- date
        for dt in {0uy..DateSize-1uy} do
            trans.age.[int(dt)] <- int(trans_age dt)
        trans.used <- 0u
        trans.read_nb <- 0L
        trans.read_hit <- 0L
        trans.write_nb <- 0L
        trans.write_hit <- 0L
        trans.write_collision <- 0L
    let trans_inc_date trans = trans_set_date ((trans.tdate+1uy) % DateSize)
    let trans_clear () =
        trans_set_date 0uy
        let entries = trans.table
        for index in {0u..trans.size-1u} do
            entries.[int(index)] <- clear_entry
    let trans_is_ok () =
        let tst i a = (a = int(trans_age (byte(i))))
        trans.size <> 0u && trans.mask <> 0u && trans.tdate < DateSize &&
        Array.fold (&&) true (Array.mapi tst trans.age)
